package net.takela.common.security.service;

import net.takela.common.security.model.AuthUser;
import net.takela.common.security.model.UserAuthToken;
import org.springframework.core.log.LogMessage;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.CookieTheftException;
import org.springframework.security.web.authentication.rememberme.InvalidCookieException;
import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationException;
import org.springframework.util.Assert;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;

public class TokenRememberServiceImpl extends AbstractRememberMeServices {
    private UserAuthTokenService userAuthTokenService;

    private SecureRandom random;

    public static final int DEFAULT_SERIES_LENGTH = 16;

    public static final int DEFAULT_TOKEN_LENGTH = 16;

    private int seriesLength = DEFAULT_SERIES_LENGTH;

    private int tokenLength = DEFAULT_TOKEN_LENGTH;
    /**
     * 
     */
    public TokenRememberServiceImpl(String key, UserDetailsService userDetailsService, UserAuthTokenService userAuthTokenService) {
        super(key, userDetailsService);
        this.setAlwaysRemember(true);
        this.random = new SecureRandom();
        this.userAuthTokenService = userAuthTokenService;
    }

    /**
     * 从token数据库里读取用户信息
     * @param cookieTokens
     * @param request
     * @param response
     * @return
     */
    @Override
    protected UserDetails processAutoLoginCookie(String[] cookieTokens, HttpServletRequest request,
                                                 HttpServletResponse response) {
        if (cookieTokens.length != 2) {
            throw new InvalidCookieException("Cookie token did not contain " + 2 + " tokens, but contained '"
                    + Arrays.asList(cookieTokens) + "'");
        }
        String presentedSeries = cookieTokens[0];
        String presentedToken = cookieTokens[1];
        UserAuthToken token = this.userAuthTokenService.getTokenByClientId(presentedSeries);
        if (token == null) {
            // No series match, so we can't authenticate using this cookie
            throw new RememberMeAuthenticationException("No persistent token found for series id: " + presentedSeries);
        }
        // We have a match for this user/series combination
        if (!presentedToken.equals(token.getToken())) {
            // Token doesn't match series value. Delete all logins for this user and throw
            // an exception to warn them.
            this.userAuthTokenService.removeToken(token.getUid());
            throw new CookieTheftException(this.messages.getMessage(
                    "PersistentTokenBasedRememberMeServices.cookieStolen",
                    "Invalid remember-me token (Series/token) mismatch. Implies previous cookie theft attack."));
        }
        if (token.getUpdateTime().getTime() + getTokenValiditySeconds() * 1000L < System.currentTimeMillis()) {
            throw new RememberMeAuthenticationException("Remember-me login has expired");
        }
        // Token also matches, so login is valid. Update the token value, keeping the
        // *same* series number.
        this.logger.debug(LogMessage.format("Refreshing persistent login token for user '%s', series '%s'",
                token.getUid(), token.getClientId()));
        UserAuthToken newToken = new UserAuthToken();
        newToken.setUid(token.getUid());
        newToken.setClientId(token.getClientId());
        newToken.setToken(generateTokenData());
        newToken.setUpdateTime(new Date());
        try {
            this.userAuthTokenService.updateToken(newToken);
            addCookie(newToken, request, response);
        }
        catch (Exception ex) {
            this.logger.error("Failed to update token: ", ex);
            throw new RememberMeAuthenticationException("Autologin failed due to data access problem");
        }
        UserDetailServiceImpl userDetailService = (UserDetailServiceImpl) getUserDetailsService();
        return userDetailService.loadUserByUid(token.getUid());
    }
    @Override
    protected Authentication createSuccessfulAuthentication(HttpServletRequest request, UserDetails user) {
        RememberMeAuthenticationToken auth = new RememberMeAuthenticationToken(getKey(), user, user.getAuthorities());
        return auth;
    }
    /**
     * 登录成功记录
     * @param request
     * @param response
     * @param successfulAuthentication
     */
    @Override
    protected void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
                                  Authentication successfulAuthentication) {
        String username = successfulAuthentication.getName();
        AuthUser user = (AuthUser)successfulAuthentication.getPrincipal();
        UserAuthToken newToken = new UserAuthToken();
        newToken.setUid(user.getUid());
        newToken.setClientId(generateSeriesData());
        newToken.setToken(generateTokenData());
        newToken.setUpdateTime(new Date());
        this.logger.debug(LogMessage.format("Creating new persistent login for user %s", username));

        try {
            this.userAuthTokenService.addToken(newToken);
            addCookie(newToken, request, response);
        }
        catch (Exception ex) {
            this.logger.error("Failed to save persistent token ", ex);
        }
    }

    
    /**
     * 
     */
    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
        super.logout(request, response, authentication);
        if (authentication != null) {
            AuthUser user = (AuthUser)authentication.getDetails();
            this.userAuthTokenService.removeToken(user.getUid());
        }
    }

    protected String generateSeriesData() {
        byte[] newSeries = new byte[this.seriesLength];
        this.random.nextBytes(newSeries);
        return new String(Base64.getEncoder().encode(newSeries));
    }

    protected String generateTokenData() {
        byte[] newToken = new byte[this.tokenLength];
        this.random.nextBytes(newToken);
        return new String(Base64.getEncoder().encode(newToken));
    }

    private void addCookie(UserAuthToken token, HttpServletRequest request, HttpServletResponse response) {
        setCookie(new String[] { token.getClientId(), token.getToken() }, getTokenValiditySeconds(), request,
                response);
    }
    /**
     * 
     */
    public void setSeriesLength(int seriesLength) {
        this.seriesLength = seriesLength;
    }
    /**
     * 
     */
    public void setTokenLength(int tokenLength) {
        this.tokenLength = tokenLength;
    }

    
    /**
     * 
     */
    @Override
    public void setTokenValiditySeconds(int tokenValiditySeconds) {
        Assert.isTrue(tokenValiditySeconds > 0, "tokenValiditySeconds must be positive for this implementation");
        super.setTokenValiditySeconds(tokenValiditySeconds);
    }
}
